package com.magenta.maxoptra.routing;

import com.magenta.maxoptra.gis.*;
import com.magenta.maxoptra.gis.commons.Logger;
import com.magenta.maxoptra.gis.commons.Quadruple;
import com.magenta.maxoptra.gis.commons.Triple;
import com.magenta.maxoptra.gis.util.Apis;
import com.magenta.mrs.util.GeoInformationClient;

import javax.ejb.EJB;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;

import static com.magenta.maxoptra.gis.utils.GeoProviderSettingsUtils.isExpiredCacheEntry;

/*
* Project: MaxOptRA v1.2
* User: Ivan Usalko
* Date: 13.09.2009 21:41:31
*
* Copyright (c) 1999-2009 Magenta Corporation Ltd. All Rights Reserved.
* Magenta Technology proprietary and confidential.
* Use is subject to license terms.
*/

/**
 * Abstract class describing workflow process of geo-info request messages processing.<br>
 * <br>
 * Class have abstract methods:<br>
 * <li>Checking message on errors. {@link #checkMessage}</li>
 * <li>Parsing message {@link #parseMessage} as result expect fulfil fields: {@link #from} and {@link #to}.</li>
 * <li>Make decision of stoping workflow after error {@link #stopWorkflowAfterException}.</li>
 */
public abstract class AbstractRequestWorkflow implements MessageListener {

    public static final String CONNECTION_ID = "connection_id";

    public static final String PROVIDER_NAME = "provider_name";

    public static final String VERSION_API = "version_api";

    public static final String CACHE_STORAGE_TIME = "cache_storage_time";

    protected Logger log = new Logger(getClass());

    @EJB
    SegmentedCache segmentedCache;

    //@EJB
    //ConnectionPool connPool;

    @EJB
    AccessToGeoProvider accessToGeoProvider;

    @EJB
    PersistedCache persistedCache;

    /**
     * Setup "from" in {@link #parseMessage} method.
     */
    RoutingCoordinate from;
    /**
     * Setup "to" in {@link #parseMessage} method.
     */
    RoutingCoordinate to;

    abstract protected boolean checkMessage(Message message);

    /**
     * Setup from and to objects.
     *
     * @param message for parsing.
     * @return true if success.
     * @throws javax.jms.JMSException throw if parsing process failed.
     */
    abstract protected boolean parseMessage(Message message) throws JMSException;

    /**
     * If it a hard failure exception then workflow process stop.
     *
     * @param e request geo-info workflow exception.
     * @return true if workflow stop.
     */
    abstract protected boolean stopWorkflowAfterException(GeoException e);

    //=====================================================
    // Getters and setters for payload fields.
    //=====================================================

    public RoutingCoordinate getFrom() {
        return from;
    }

    public void setFrom(RoutingCoordinate from) {
        this.from = from;
    }

    public RoutingCoordinate getTo() {
        return to;
    }

    public void setTo(RoutingCoordinate to) {
        this.to = to;
    }

    //=====================================================
    // Basic workflow method.
    //=====================================================

    public void onMessage(Message message) {
        try {
            if (checkMessage(message)) {
                //FIND PROVIDER
                if (!message.propertyExists(CONNECTION_ID)) {
                    throw new IllegalStateException("PROPERTY CONNECTION_ID NOT FOUND!");
                }
                long connectionId = message.getLongProperty(CONNECTION_ID);
                IGeoRouting routingService = accessToGeoProvider.getGeoRoutingProvider(connectionId);
                //FIND LOCAL ROUTES CACHE
                if (!message.propertyExists(PROVIDER_NAME)) {
                    throw new IllegalStateException("PROPERTY PROVIDER_KEY NOT FOUND!");
                }
                String providerName = message.getStringProperty(PROVIDER_NAME);
                String versionApi = message.getStringProperty(VERSION_API);
//                String discriminator = null;

                // GET MAX AGE OF ENTRIES OF CACHE
                String cacheStorageTime = null;
                GisParameters gisParameters = null;
                if (routingService instanceof IGeoConfigurable) {
                    GeoInformationClient client = ((IGeoConfigurable) routingService).getClient();
                    if (client != null) {
                        gisParameters = client.getProperties();
                        if (gisParameters != null) {
                            cacheStorageTime = gisParameters.get(GisParameter.cacheStorageTime.withProvider(providerName.toLowerCase()));
                        }
                    }
                }

//                properties.
                GeoProviderSettings settings = new GeoProviderSettings(providerName, versionApi, gisParameters);
                RoutesCache routesCache = segmentedCache.getSegment(settings);

                //PARSE MESSAGE
                if (!parseMessage(message)) {
                    throw new IllegalStateException("UNPARSEABLE MESSAGE!");
                }

                //DUPLICATED MESSAGES REMOVE, TODO: IMPLEMENTATION FULL FUNCTIONAL SYNCHRONIZED MECHANIZM BETWEEN MDB
                Triple<Double, Long, Long> cachedInfo = routesCache.getWithAge(from, to);
                if (cachedInfo != null) {
                    return;
                }
                Long cid = Apis.cid(providerName, versionApi, settings.getCidDiscriminator());
                Quadruple<Double, Long, Long, Integer> persistedCachedInfo = persistedCache.findRouteByCID(cid, from, to);
                if (persistedCachedInfo != null && persistedCachedInfo.getFirst() > 0
                        && !isExpiredCacheEntry(cacheStorageTime, persistedCachedInfo.getThird())) {
                    routesCache.put(from, to, Triple.generate(persistedCachedInfo.getFirst(), persistedCachedInfo.getSecond(), persistedCachedInfo.getThird()));
                    return;
                }

                //GETTING ROUTE INFO
                GeoRoute routeInfo = null;
                long requestDuration = 0;
                try {
//                    routeInfo = routingService.getRoute(getGeoCoordinate(from), getGeoCoordinate(to));
                    long moment = System.currentTimeMillis();
                    routeInfo = routingService.getRoute(from.getGeoCoordinate(), to.getGeoCoordinate());
                    requestDuration = System.currentTimeMillis() - moment;
                } catch (GeoException e) {
                    if (stopWorkflowAfterException(e)) {
                        throw new IllegalStateException(e);
                    }
                } finally {
                    GeoProviderAccessStatistics.markAccessNow(cid);
                }
//                //STORE RESULT INTO LOCAL IN MEMORY CACHE
//                float latFrom = (float) from.getLatitude();
//                float latTo = (float) to.getLatitude();
//                float lonFrom = (float) from.getLongitude();
//                float lonTo = (float) to.getLongitude();
                Double distance = routeInfo != null ? routeInfo.getDistanceInKM() : -1d;
                Long duration = routeInfo != null ? routeInfo.getDurationInMillis() : -1L;
                if (routeInfo != null) {
                    requestDuration = routeInfo.getRequestDuration();
                }
                if (distance == 0 || duration == 0) {
                    distance = (double) GeoStatusCode.G_GEO_RESULT_IS_NULL.getCode();
                    duration = (long) GeoStatusCode.G_GEO_RESULT_IS_NULL.getCode();
                }
                Triple<Double,Long,Long> triple = Triple.generate(distance, duration, System.currentTimeMillis());
                Quadruple<Double,Long,Long, Integer> quadruple = Quadruple.generate(triple, (int)requestDuration);
                routesCache.put(from, to, triple);
//                routesCache.put(latFrom, lonFrom, latTo, lonTo, new Pair<Double,Long>(distance, duration));
                //PERSIST RESULT
                //IGeoConfigurable config = (IGeoConfigurable) routingService;
                //Long cid = config.classId();
                RouteRequestRecordPK pk = new RouteRequestRecordPK(providerName, versionApi, settings.getCidDiscriminator(),
                        from.getLatitudeInteger(),
                        from.getLongitudeInteger(),
                        to.getLatitudeInteger(),
                        to.getLongitudeInteger());
                persistedCache.persistRouteInfo(pk, quadruple);
            }
        } catch (JMSException e) {
            log.warnX("ERROR IN BASIC REQUEST WORKFLOW!", e);
        } catch (IllegalStateException e) {
            log.warnX("ILLEGAL REQUEST WORKFLOW STATE", e);
        }
        //TODO: remove below lines (temporare debug stub).
        catch (Exception e) {
            log.warnX("UNKNOWN REQUEST WORKFLOW STATE", e);
        }
    }
}